module Project3(SW,KEY,LEDR,LEDG,HEX0,HEX1,HEX2,HEX3,CLOCK_50);
  input  [9:0] SW;
  input  [3:0] KEY;
  input  CLOCK_50;
  output [9:0] LEDR;
  output [7:0] LEDG;
  output [6:0] HEX0,HEX1,HEX2,HEX3;

  parameter DBITS    =32;
  parameter INSTSIZE =32'd4;
  parameter INSTBITS =32;
  parameter STARTPC  =32'h20;
  parameter REGNOBITS=5;
  parameter IMMBITS=16;
  parameter ADDRKEY  =32'hFFFFFF00;
  parameter ADDRSW   =32'hFFFFFF10;
  parameter ADDRHEX =32'hFFFFFF80;
  parameter ADDRLEDR=32'hFFFFFF90;
  parameter ADDRLEDG=32'hFFFFFFA0;
  parameter IMEMINITFILE="test_cases/testlwopt.mif";
  parameter IMEMADDRBITS=13;
  parameter IMEMWORDBITS=2;
  parameter IMEMWORDS=2048;
  parameter DMEMADDRBITS=13;
  parameter DMEMWORDBITS=2;
  parameter DMEMWORDS=2048;
  
  parameter OP1_ALUR=6'o00;
  parameter OP1_ANDI=6'o01;
  parameter OP1_ORI =6'o02;
  parameter OP1_HI  =6'o03;
  parameter OP1_ADDI=6'o04;
  parameter OP1_LW  =6'o20;
  parameter OP1_SW  =6'o21;
  parameter OP1_JAL =6'o40;
  parameter OP1_BEQ =6'o41;
  parameter OP1_BNE =6'o45;
  
  parameter OP2_AND =6'o11;
  parameter OP2_OR  =6'o12;
  parameter OP2_XOR =6'o13;
  parameter OP2_NAND=6'o15;
  parameter OP2_NOR =6'o16;
  parameter OP2_NXOR=6'o17;
  parameter OP2_ADD =6'o20;
  parameter OP2_SUB =6'o21;
  parameter OP2_EQ  =6'o41;
  parameter OP2_LT  =6'o42;
  parameter OP2_LE  =6'o43;
  parameter OP2_NE  =6'o45;
  parameter OP2_GE  =6'o46;
  parameter OP2_GT  =6'o47;
  // This is not a valid opcode2 value, but it is a valid ALUfunc value
  parameter OP2_LOHI=6'o77;

  wire clk,lock;
  Pll pll(.inclk0(CLOCK_50),.c0 (clk),.locked(lock));
  //assign clk = KEY[0];
  //wire reset=!KEY[1];
  wire reset=!lock;
  
  // The PC register and update logic
  reg  [(DBITS-1):0] PC;
  // These come from the stage where the correct next PC is computed
  // Currently that is the A(LU) stage, but it can be changed
  wire [(DBITS-1):0] pcgood_A;
  wire mispred_A;
  reg [11:0] iaddr_A, iaddr_M;
  reg [(DBITS-1):0] pcgood_M;
	always @(posedge clk) begin
		if(reset)
			PC<=STARTPC;
		else if(mispred_A && !stall_F) begin
			PC<=pcgood_A;
		end
		else if(!stall_F)
			PC<=pcpred_F;
		iaddr_A <= iaddr_F;
		iaddr_M <= iaddr_A;
		/*if(flush_D || stall_F) begin
			iaddr_A <= 12'b0;
		end*/
		pcgood_M <= pcgood_A;
		bpred[iaddr_M] <= pcgood_M;
	end
  // This is the value of "incremented PC", computed in stage 1
  wire [(DBITS-1):0] pcplus_F=PC+32'd4;
  // This is the predicted value of the PC
  // that we used to fetch the next instruction
  // When we determine the correct next PC (pcgood)
  // we can compare to this to see if we should flush...
  wire [(DBITS-1):0] pcpred_F=bpred[iaddr_F];
  
  (* ram_init_file = "pcplus4.mif" *)
  reg [(DBITS-1):0] bpred[(IMEMWORDS-1):0];
  
  // Instruction-fetch
  (* ram_init_file = IMEMINITFILE *)
  reg [(DBITS-1):0] imem[(IMEMWORDS-1):0];
  wire [(DBITS-1):0] inst_F=imem[iaddr_F];
  wire [(IMEMADDRBITS-IMEMWORDBITS-1):0] iaddr_F = PC[(IMEMADDRBITS-1):IMEMWORDBITS];

	// Fetch and decoding stages are the same,
	// just connect signals from fetch to decode
	wire [(DBITS-1):0] inst_D=inst_F;
	wire [(DBITS-1):0] pcplus_D=pcplus_F;
	wire [(DBITS-1):0] pcpred_D=pcpred_F;
	reg [(DBITS-1):0] pcplus_A, pcpred_A;
	reg isnop_A;
	always @(posedge clk) begin
		pcplus_A <= pcplus_D;
		pcpred_A <= pcpred_D;
		isnop_A <= flush_D || stall_F ? 1'b1 : isnop_D;
	end

	// Instruction decoding
	// These have zero delay from inst_D
	// because they are just new names for those signals
	wire [5:0]  op1_D=inst_D[31:26];
	wire [4:0]  rx_D =inst_D[25:21];
	wire [4:0]  ry_D =inst_D[20:16];
	wire [4:0]  rz_D =inst_D[15:11];
	wire [5:0]  op2_D=inst_D[ 5: 0];
	wire [(IMMBITS-1):0] rawimm_D=inst_D[(IMMBITS-1):0];
	wire [(DBITS-1):0] sxtimm_D={ {(DBITS-IMMBITS){rawimm_D[(IMMBITS-1)]}}, rawimm_D };

	// Register-read, force it to use "logic" registers (otherwise it uses a memory array)
	(* ramstyle="logic" *)
	reg [(DBITS-1):0] regs[31:0];
	// Two read ports, always using rx and ry for register numbers
	wire [4:0] rregno1_D=rx_D, rregno2_D=ry_D;
	wire forwardr1_A = (wregno_A == rregno1_D) && wrreg_A;
	wire forwardr2_A = (wregno_A == rregno2_D) && wrreg_A;
	wire forwardr1_M = (wregno_M == rregno1_D) && wrreg_M;
	wire forwardr2_M = (wregno_M == rregno2_D) && wrreg_M;
	wire [(DBITS-1):0] regval1_D= forwardr1_A ? aluout_A :
											forwardr1_M ? wregval_M : regs[rregno1_D];
	wire [(DBITS-1):0] regval2_D= forwardr2_A ? aluout_A :
											forwardr2_M ? wregval_M : regs[rregno2_D];

	// Get these signals to the ALU somehow...
	reg signed [(DBITS-1):0] aluin1_A, aluin2_A, regval2_A;
	reg [5:0] alufunc_D, alufunc_A;
	always @(posedge clk) begin
		aluin1_A <= regval1_D;
		aluin2_A <= aluimm_D ? sxtimm_D : regval2_D;
		regval2_A <= regval2_D;
		alufunc_A <= alufunc_D;
	end
	
	
	// Now the actual ALU
	reg signed [(DBITS-1):0] aluout_A;
	always @(alufunc_A or aluin1_A or aluin2_A)
		case(alufunc_A)
		OP2_AND:  aluout_A=aluin1_A&aluin2_A;
		OP2_OR:   aluout_A=aluin1_A|aluin2_A;
		OP2_XOR:  aluout_A=aluin1_A^aluin2_A;
		OP2_NAND: aluout_A=~(aluin1_A&aluin2_A);
		OP2_NOR:  aluout_A=~(aluin1_A|aluin2_A);
		OP2_NXOR: aluout_A=~(aluin1_A^aluin2_A);
		OP2_ADD:  aluout_A=aluin1_A+aluin2_A;
		OP2_SUB:  aluout_A=aluin1_A-aluin2_A;
		OP2_EQ:   aluout_A=(aluin1_A==aluin2_A);
		OP2_LT:   aluout_A=(aluin1_A<aluin2_A);
		OP2_LE:	  aluout_A=(aluin1_A<=aluin2_A);
		OP2_NE:   aluout_A=(aluin1_A!=aluin2_A);
		OP2_GE:   aluout_A=(aluin1_A>=aluin2_A);
		OP2_GT:	  aluout_A=(aluin1_A>aluin2_A);
		OP2_LOHI: aluout_A={aluin2_A[15:0],aluin1_A[15:0]};
		default:  aluout_A={DBITS{1'bX}};
		endcase

	// Optimization for LW with 0 immediate (skip ALU stage)
	//wire skipA_D = (rawimm_D == {IMMBITS{1'b0}} && selmemout_D && !wrreg_A && !wrmem_A && !reset);
	/*reg skipA_A;
	always @(posedge clk) begin
		skipA_A <= skipA_D;
	end*/
	//reg skipA_D;
	reg skipA_A;
	always @(posedge clk)
			skipA_A <= skipA_D;// && !wrreg_A && !wrmem_A);
	
	// TODO: Generate the dobranch, brtarg, isjump, and jmptarg signals somehow...
	reg stall_F;
	//wire dobranch_A = isbranch_A && aluout_A[0];
	wire dobranch_A = (isbranch_A && ((alufunc_A == OP2_EQ && aluin1_A == aluin2_A) || (alufunc_A == OP2_NE && aluin1_A != aluin2_A))) ? 1'b1 : 1'b0;
	reg isjump_A, isbranch_A;
	reg [31:0] shimm_A;
	always @(posedge clk) begin
		isjump_A <= isjump_D;
		isbranch_A <= isbranch_D;
		shimm_A <= {sxtimm_D[(DBITS-3):0],2'h0};
		if(stall_F) begin
			isbranch_A <= 1'b0;
		end
	end
	assign pcgood_A=
		dobranch_A?brtarg_A:
		isjump_A?jmptarg_A:
		pcplus_A;
	assign mispred_A=(pcgood_A!=pcpred_A)&&!isnop_A&&isbranch_A;
	reg [(DBITS-1):0] jmptarg_A, brtarg_A;
	always @(aluin1_A or shimm_A)
		jmptarg_A = aluin1_A + shimm_A;
	always @(pcplus_A or shimm_A)
		brtarg_A = pcplus_A + shimm_A;
  
	// This is a good place to generate the flush_? signals
	// stall if: RR stage needs value from a load word (memory access doesn't happen until M stage)
	// flush if: branch misprediction
	always @(rregno1_D or rregno2_D or wregno_A or wrreg_A or wregno_M or wrreg_M or mispred_A or selmemout_A or selmemout_M) begin
		stall_F = 1'b0;
		/*flush_D = 1'b0;*/
		/*if (((rregno1_D == wregno_A || rregno2_D == wregno_A) && wrreg_A)
		||  ((rregno1_D == wregno_M || rregno2_D == wregno_M) && wrreg_M))
			stall_F = 1'b1;*/
		if (((rregno1_D == wregno_A || rregno2_D == wregno_A) && wrreg_A && selmemout_A)
		||  ((rregno1_D == wregno_M || rregno2_D == wregno_M) && wrreg_M && selmemout_M))
			stall_F = 1'b1;
	end

	// produces wmemval_M, wrmem_M, wrreg_M, etc.
	reg [(DBITS-1):0] wmemval_M;
	reg [(DBITS-1):0] memaddr_M;
	reg [(REGNOBITS-1):0] wregno_M, wregno_A;
	reg wrmem_M, wrreg_M, wrmem_A, wrreg_A, wrmem_D, wrreg_D;
	always @(posedge clk or posedge reset) begin
		if(reset) begin
			wmemval_M <= {DBITS{1'h0}};
			wrmem_M <= 1'b0;
			wrreg_M <= 1'b0;
			memaddr_M <= {DMEMADDRBITS{1'h0}};
			wregno_M <= {REGNOBITS{1'h0}};
		end
		else begin
			wrmem_A <= skipA_A ? 1'b0 : wrmem_D;
			wrreg_A <= skipA_A ? 1'b0 : wrreg_D;
			wrmem_M <= skipA_A ? 1'b1 : wrmem_A;
			wrreg_M <= wrreg_A;
			wmemval_M <= regval2_A;
			memaddr_M <= skipA_A ? regval2_D + sxtimm_D : aluout_A;
			wregno_A <= wregno_D;
			wregno_M <= skipA_A ? wregno_D : wregno_A;
			/*if (stall_A) begin
				wrmem_M <= 1'b0;
				wrreg_M <= 1'b0;
			end*/
			if (stall_F || flush_D) begin
				wrmem_A <= 1'b0;
				wrreg_A <= 1'b0;
			end
		end
	end
	
	// Create and connect HEX register
	reg [15:0] HexOut;
	SevenSeg ss3(.OUT(HEX3),.IN(HexOut[15:12]));
	SevenSeg ss2(.OUT(HEX2),.IN(HexOut[11:8]));
	SevenSeg ss1(.OUT(HEX1),.IN(HexOut[7:4]));
	SevenSeg ss0(.OUT(HEX0),.IN(HexOut[3:0]));
	always @(posedge clk or posedge reset)
		if(reset)
			HexOut<=16'hDEAF;
		else if(wrmem_M&&(memaddr_M==ADDRHEX))
			HexOut<=wmemval_M[15:0];
		//HexOut<={1'b0,op1_D[5:3],1'b0,op1_D[2:0],1'b0,op2_D[5:3],1'b0,op2_D[2:0]};
		//HexOut<=aluout_A[15:0];
		//HexOut<=wregval_M[15:0];
		//HexOut<=regs[8];
		//HexOut<=memaddr_M[15:0];
			
	// LEDG and LEDR devices
	reg [9:0] LEDRout;
	reg [7:0] LEDGout;
	assign LEDR = LEDRout;
	assign LEDG = LEDGout;
	always @(posedge clk or posedge reset) begin
		if(reset) begin
			LEDRout <= 10'h0;
			LEDGout <= 8'h0;
		end
		/*
		else begin
			LEDRout <= {rregno1_D, rregno2_D};
			LEDGout[4:0] <= wregno_M;
			//LEDGout[5] <= wrreg_M;
			LEDGout[5] <= MemWE;
			LEDGout[7:6] <= {selaluout_D, selaluout_M};
		end
		*/
		// do memaddr and ADDRLEDR need to match in length?
		else if(wrmem_M && memaddr_M == ADDRLEDR) begin
			LEDRout <= wmemval_M[9:0];
		end
		else if(wrmem_M && memaddr_M == ADDRLEDG) begin
			LEDGout <= wmemval_M[7:0];
		end
	end

	// Now the real data memory
	wire MemEnable=!(memaddr_M[(DBITS-1):DMEMADDRBITS]);
	wire MemWE=lock&wrmem_M&MemEnable;
	(* ramstyle="no_rw_check" *)
	reg [(DBITS-1):0] dmem[(DMEMWORDS-1):0];
	always @(posedge clk)
		if(MemWE)
			dmem[memaddr_M[(DMEMADDRBITS-1):DMEMWORDBITS]]<=wmemval_M;
	wire [(DBITS-1):0] MemVal=MemWE?{DBITS{1'bX}}:dmem[memaddr_M[(DMEMADDRBITS-1):DMEMWORDBITS]];
	// Connect memory and input devices to the bus
	wire [(DBITS-1):0] memout_M=
		MemEnable?MemVal:
		(memaddr_M==ADDRKEY)?{28'b0,~KEY}:
		(memaddr_M==ADDRSW)? {22'b0,SW}:
		32'hDEADBEEF;

	// TODO: Decide what gets written into the destination register (wregval_M)
	reg [(DBITS-1):0] wregval_M, aluout_M;
	reg [(DBITS-1):0] pcplus_M;
	reg selaluout_M, selmemout_M, selpcplus_M, selaluout_A, selmemout_A, selpcplus_A, selaluout_D, selmemout_D, selpcplus_D;
	always @(posedge clk or posedge reset) begin
		if (reset) begin
			pcplus_M <= {(IMEMADDRBITS){1'b0}};
			aluout_M <= {(DBITS){1'b0}};
			selaluout_A <= 1'b0;
			selmemout_A <= 1'b0;
			selpcplus_A <= 1'b0;
			selaluout_M <= 1'b0;
			selmemout_M <= 1'b0;
			selpcplus_M <= 1'b0;
		end
		else begin
			pcplus_M <= pcplus_A;
			aluout_M <= aluout_A;
			selaluout_A <= selaluout_D;
			selmemout_A <= skipA_A ? 1'b1 : selmemout_D;
			selpcplus_A <= selpcplus_D;
			selaluout_M <= selaluout_A;
			selmemout_M <= selmemout_A;
			selpcplus_M <= selpcplus_A;
		end
	end
	always @(selaluout_M or selmemout_M or selpcplus_M or reset or aluout_M or memout_M or pcplus_M) begin
		if (reset)
			wregval_M = {(DBITS){1'b0}};
		else if (selaluout_M)
			wregval_M = aluout_M;
		else if (selmemout_M)
			wregval_M = memout_M;
		else if (selpcplus_M)
			wregval_M = pcplus_M;
		else
			wregval_M = {(DBITS){1'bX}};
	end

	always @(posedge clk)
		if(wrreg_M&&lock)
			regs[wregno_M]<=wregval_M;

  // Decoding logic
  reg isnop_D, aluimm_D, isbranch_D, isjump_D, skipA_D;
  wire flush_D = mispred_A;
  reg [(REGNOBITS-1):0] wregno_D;
  always @(inst_D or op1_D or op2_D or ry_D or rz_D or flush_D) begin
	{aluimm_D,alufunc_D,isbranch_D,isjump_D,isnop_D,wrmem_D}=
	{    1'bX,    6'hXX,      1'b0,    1'b0,   1'b0,   1'b0};
	{selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D,skipA_D}=
	{       1'bX,       1'bX,       1'bX,   5'hXX,   1'b0,   1'b0};
	case(op1_D)
		OP1_ALUR:
			if(inst_D==32'b0)
				isnop_D=1'b1;
			else
				{aluimm_D,alufunc_D,selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D}=
				{    1'b0,    op2_D,       1'b1,       1'b0,       1'b0,    rz_D,   1'b1};
		OP1_ANDI:
			{aluimm_D,alufunc_D,selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D}=
			{    1'b1,  OP2_AND,       1'b1,       1'b0,       1'b0,    ry_D,   1'b1};
		OP1_ORI:
			{aluimm_D,alufunc_D,selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D}=
			{    1'b1,   OP2_OR,       1'b1,       1'b0,       1'b0,    ry_D,   1'b1};
		OP1_HI:
			{aluimm_D,alufunc_D,selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D}=
			{    1'b1, OP2_LOHI,       1'b1,       1'b0,       1'b0,    ry_D,   1'b1};
		OP1_ADDI:
			{aluimm_D,alufunc_D,selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D}=
			{    1'b1,  OP2_ADD,       1'b1,       1'b0,       1'b0,    ry_D,   1'b1};
		OP1_LW:
			{aluimm_D,alufunc_D,selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D,skipA_D}=
			{    1'b1,  OP2_ADD,       1'b0,       1'b1,       1'b0,    ry_D,   1'b1,   1'b1};
		OP1_SW:
			{aluimm_D,alufunc_D,selaluout_D,selmemout_D,selpcplus_D,wrmem_D}=
			{    1'b1,  OP2_ADD,       1'b0,       1'b0,       1'b0,   1'b1};
		OP1_JAL: // ry<=PC+4; PC<=rx+(sxt(imm)*4)
			{isjump_D,aluimm_D,selaluout_D,selmemout_D,selpcplus_D,wregno_D,wrreg_D}=
			{    1'b1,    1'b1,       1'b0,       1'b0,       1'b1,    ry_D,   1'b1};
		OP1_BEQ: // if(rx == ry) PC=PC+4+(sxt(imm)*4)
			{isbranch_D,aluimm_D,alufunc_D}=
			{      1'b1,    1'b0,  OP2_EQ};
		OP1_BNE: // if(rx != ry) PC=PC+4+(sxt(imm)*4)
			{isbranch_D,aluimm_D,alufunc_D}=
			{      1'b1,    1'b0,  OP2_NE};
		default:  ;
	 endcase
  end
endmodule
